import { zodResolver } from '@hookform/resolvers/zod'
import {
	Button,
	Card,
	CardActionArea,
	CardActions,
	CardContent,
	CardMedia,
	Dialog,
	Grid,
	LinearProgress,
	Skeleton,
	Tooltip,
	Typography
} from '@mui/material'
import Box from '@mui/material/Box'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
import {
	CalendarPlus,
	Check,
	Download,
	File,
	FileArchive,
	FileAudio,
	FileText,
	FileVideo,
	Pencil,
	Trash,
	Weight,
	X
} from 'lucide-react'
import { useCallback, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'

import { AutocompleteControlled } from '@/components/forms/AutocompleteControlled'
import { TextFieldControlled } from '@/components/forms/TextFieldControlled'

import {
	type IAttach,
	type IAttachGroups,
	type IAttachPreview,
	type IAttachUpdate,
	attachUpdateScheme
} from '@/types/attach.types'

import { axiosWithAuth } from '@/api/interceptors'

import { attachService } from '@/services/attach.service'

type AttachData = IAttach | IAttachPreview

function getFileIcon(contentType: string) {
	if (contentType.startsWith('video/')) {
		return <FileVideo size={140} />
	} else if (contentType.startsWith('audio/')) {
		return <FileAudio size={140} />
	} else if (contentType.startsWith('text/')) {
		return <FileText size={140} />
	} else if (
		contentType === 'application/zip' ||
		contentType === 'application/x-zip-compressed' ||
		contentType === 'application/x-rar-compressed' ||
		contentType === 'application/x-7z-compressed' ||
		contentType === 'application/x-tar'
	) {
		return <FileArchive size={140} />
	} else {
		return <File size={140} />
	}
}

export function AttachCard({
	attach_data,
	queryKey,
	upload
}: {
	attach_data: AttachData | undefined
	queryKey: [string, string, number]
	upload?: {
		handleSuccess: (id: number) => void
		handleDecline: (id: number) => void
	}
}) {
	const queryClient = useQueryClient()
	const [attach, setAttach] = useState<AttachData | undefined>(attach_data)
	const [open, setOpen] = useState(false)
	const [imageUrl, setImageUrl] = useState<string | null>(null)
	const [editMode, setEditMode] = useState<boolean>(false)
	const [groupSearchOptions, setGroupSearchOptions] = useState<
		{ name: string; id: number | null }[]
	>([])

	const isIAttach = useCallback(
		(obj: any): obj is IAttach => 'creator' in obj,
		[attach]
	)

	const {
		formState: { isValid, isDirty },
		control,
		handleSubmit,
		reset
	} = useForm<IAttachUpdate>({
		mode: 'onChange',
		resolver: zodResolver(attachUpdateScheme),
		defaultValues: attach
			? {
					...attach,
					group: attach.group?.id ?? null,
					url: attach.url || undefined
				}
			: undefined
	})

	const handleOpen = useCallback(() => {
		if (attach) setOpen(true)
	}, [attach])

	const handleClose = useCallback(() => {
		setOpen(false)
	}, [])

	useEffect(() => {
		if (
			attach &&
			isIAttach(attach) &&
			attach.content_type.startsWith('image/')
		) {
			fetchImage().then(setImageUrl)
		} else setImageUrl(attach?.url || null)
	}, [attach])

	useEffect(() => {
		if (!attach_data) return
		setAttach(attach_data)
		reset({
			...attach_data,
			group: attach_data.group?.id ?? null,
			url: attach_data.url || undefined
		})
	}, [attach_data])

	const [isPending, setIsPending] = useState(false)
	const [progress, setProgress] = useState(0)
	const [cancel, setCancel] = useState<(() => void) | null>(null)

	const handleDelete = useCallback(async () => {
		if (!attach) return
		setIsPending(true)

		await attachService
			.del(attach.id, { name: queryKey[1], id: queryKey[2] })
			.then(() => {
				setIsPending(false)
				toast.success('Файл удален!', {
					description: attach.original_name
				})

				queryClient.setQueryData(queryKey, (old: IAttach[] | undefined) =>
					old && old.length > 0
						? old.filter(old_attach => old_attach.id !== attach.id)
						: []
				)
			})
			.catch(() => {
				setIsPending(false)
				toast.error('Не удалось удалить файл!', {
					description: attach.original_name
				})
			})
	}, [attach, queryKey])

	const handleUpdate = useMutation({
		mutationKey: ['attach', 'update', attach?.id],
		mutationFn: (data: IAttachUpdate) =>
			attach
				? attachService.update(attach.id, data)
				: Promise.resolve(undefined),
		onSuccess: data => {
			toast.success('Информация о файле изменена!', {
				description: attach?.original_name
			})

			setEditMode(false)

			if (data)
				queryClient.setQueryData(queryKey, (old: IAttach[] | undefined) =>
					old && old.length > 0
						? old.map(old_attach =>
								old_attach.id === attach?.id ? data : old_attach
							)
						: []
				)
		},
		onError: () => {
			toast.error('Не удалось изменить файл!', {
				description: attach?.original_name
			})
		}
	})

	const fetchImage = useCallback(async () => {
		if (!attach) return ''
		const response = await axiosWithAuth.get(`/attachments/${attach.id}/file`, {
			responseType: 'blob'
		})
		const blob = new Blob([response.data], { type: 'image/jpeg' })
		return URL.createObjectURL(blob)
	}, [attach])

	const downloadFile = useCallback(async () => {
		if (!attach) return

		setIsPending(true)
		setProgress(0)

		const CancelToken = axios.CancelToken
		const source = CancelToken.source()
		setCancel(() => source.cancel)

		try {
			await axiosWithAuth
				.get(`/attachments/${attach.id}/file`, {
					responseType: 'blob',
					cancelToken: source.token,
					onDownloadProgress: progressEvent => {
						if (progressEvent)
							setProgress(
								Math.round(
									(progressEvent.loaded * 100) / (progressEvent.total || 1)
								)
							)
					}
				})
				.then(response => {
					// create file link in browser's memory
					const href = URL.createObjectURL(response.data)
					// create "a" HTML element with href to file & click
					const link = document.createElement('a')
					link.href = href
					link.setAttribute('download', attach.original_name) //or any other extension
					document.body.appendChild(link)
					link.click()
					// clean up "a" element & remove ObjectURL
					document.body.removeChild(link)
					URL.revokeObjectURL(href)
				})
		} catch (error) {
			if (!axios.isCancel(error)) {
				toast.error('Не удалось скачать файл!', {
					description: attach.original_name
				})
				console.error('Error during file download: ', error)
			}
		} finally {
			setIsPending(false)
			setCancel(null)
		}
	}, [attach])

	const startUpload = useCallback(
		async (file: Blob, form_data: IAttachUpdate) => {
			if (!upload) return

			setIsPending(true)
			setProgress(0)

			const data = new FormData()
			data.append('path', file)
			data.append(queryKey[1], queryKey[2].toString())
			data.append('original_name', form_data.original_name)
			data.append('group', form_data.group?.toString() ?? '')
			data.append(queryKey[0], queryKey[1])

			const CancelToken = axios.CancelToken
			const source = CancelToken.source()
			setCancel(() => source.cancel)

			try {
				const request = await axiosWithAuth.post<IAttach>(
					'/attachments/',
					data,
					{
						headers: {
							'Content-Type': 'multipart/form-data'
						},
						cancelToken: source.token,
						onUploadProgress: progressEvent => {
							if (progressEvent)
								setProgress(
									Math.round(
										(progressEvent.loaded * 100) / (progressEvent.total || 1)
									)
								)
						}
					}
				)

				queryClient.setQueryData(queryKey, (old: IAttach[] | undefined) =>
					old && old.length > 0 ? old.concat(request.data) : []
				)
			} finally {
				setIsPending(false)
				setCancel(null)
			}
		},
		[queryKey, upload]
	)

	const handleDecline = useCallback(() => {
		if (!upload || !attach) return
		upload.handleDecline(attach.id)
	}, [upload, attach])

	const generateSearchResults = useCallback((data: IAttachGroups) => {
		setGroupSearchOptions(
			data.results.map(group => ({
				name: group.name,
				id: group.id
			}))
		)
	}, [])
	const searchGroups = useMutation({
		mutationKey: ['search_attach_groups'],
		mutationFn: (name: string) => attachService.fetchManyGroup(name),
		onSuccess: data => generateSearchResults(data)
	})

	const onSubmit = useCallback(
		(data: IAttachUpdate) => {
			if (!upload || !attach || isIAttach(attach)) handleUpdate.mutate(data)
			else {
				startUpload(attach.file, data)
					.then(() => {
						toast.success('Файл загружен!', {
							description: attach.original_name
						})
						upload.handleSuccess(attach.id)

						queryClient.invalidateQueries({ queryKey: queryKey })
					})
					.catch(err => {
						if (!axios.isCancel(err)) {
							toast.error('Не удалось загрузить файл!', {
								description: attach.original_name
							})
							console.error(err)
						}
					})
			}
		},
		[handleUpdate]
	)

	return (
		<Card sx={{ maxWidth: 280 }}>
			{attach?.url && imageUrl ? (
				attach.content_type.startsWith('image/') ? (
					<CardActionArea onClick={handleOpen}>
						<CardMedia
							sx={{ height: 140 }}
							image={imageUrl}
						/>
					</CardActionArea>
				) : (
					<Box sx={{ display: 'flex', justifyContent: 'center', opacity: 0.8 }}>
						{getFileIcon(attach.content_type)}
					</Box>
				)
			) : (
				<Skeleton
					variant='rectangular'
					width={345}
					height={140}
				/>
			)}
			{attach?.url && imageUrl && (
				<Dialog
					open={open}
					onClose={handleClose}
					maxWidth='md'
					fullWidth
				>
					<img
						src={imageUrl}
						alt='image'
						style={{ width: '100%', height: 'auto' }}
					/>
				</Dialog>
			)}
			<CardContent>
				{attach && (editMode || !isIAttach(attach)) ? (
					<Grid
						container
						spacing={2}
					>
						<Grid
							item
							xs={12}
						>
							<TextFieldControlled
								control={control}
								name='original_name'
								label='Имя'
								multiline
							/>
						</Grid>
						<Grid
							item
							xs={12}
						>
							<AutocompleteControlled
								control={control}
								callback={{
									mutate: searchGroups.mutate,
									options: groupSearchOptions,
									isPending: searchGroups.isPending,
									enabled: true
								}}
								name='group'
								label='Группа'
								defVal={{
									exists: attach.group !== null,
									value: attach.group
										? {
												id: attach.group.id,
												name: attach.group.name
											}
										: undefined
								}}
							/>
						</Grid>
					</Grid>
				) : attach?.original_name ? (
					<Typography
						gutterBottom
						variant='h5'
						component='div'
						sx={{ wordWrap: 'break-word' }}
					>
						{attach.original_name}
					</Typography>
				) : (
					<Skeleton />
				)}
				{attach ? (
					<Box sx={{ paddingTop: 2, color: 'text.secondary' }}>
						<Tooltip
							title='Размер файла'
							placement='left-start'
							arrow
						>
							<Typography>
								<Weight
									size={18}
									style={{ display: 'inline', marginRight: '5px' }}
								/>
								{(attach.size / 1000000).toFixed(2)} МБ
							</Typography>
						</Tooltip>
						{isIAttach(attach) && (
							<Tooltip
								title='Дата загрузки'
								placement='left-start'
								arrow
							>
								<Typography>
									<CalendarPlus
										size={18}
										style={{ display: 'inline', marginRight: '5px' }}
									/>
									{attach.created_on.format('HH:mm DD.MM.YYYY')}
								</Typography>
							</Tooltip>
						)}
					</Box>
				) : (
					<Skeleton />
				)}
			</CardContent>
			{attach && (
				<CardActions>
					{isIAttach(attach) ? (
						<>
							{isPending ? (
								cancel && (
									<Button
										size='small'
										startIcon={<X />}
										color='error'
										onClick={() => cancel()}
									>
										Отменить скачивание
									</Button>
								)
							) : (
								<>
									<Button
										size='small'
										startIcon={<Download />}
										onClick={() => downloadFile()}
										disabled={isPending || editMode}
									/>
									{editMode && (
										<Button
											size='small'
											startIcon={<Check />}
											color='success'
											onClick={handleSubmit(onSubmit)}
											disabled={handleUpdate.isPending || !isValid || !isDirty}
										/>
									)}
									<Button
										size='small'
										startIcon={editMode ? <X /> : <Pencil />}
										color={editMode ? 'error' : 'primary'}
										disabled={isPending || handleUpdate.isPending}
										onClick={() => {
											setEditMode(!editMode)
											reset(
												isIAttach(attach)
													? { ...attach, group: attach.group?.id }
													: undefined
											)
										}}
									/>
									<Button
										size='small'
										startIcon={<Trash />}
										color='secondary'
										disabled={isPending || editMode}
										onClick={handleDelete}
									/>
								</>
							)}
						</>
					) : (
						<>
							{upload?.handleSuccess && (
								<Button
									size='small'
									startIcon={<Check />}
									color='success'
									onClick={handleSubmit(onSubmit)}
									disabled={isPending || !isValid}
								>
									Подтвердить
								</Button>
							)}
							{upload?.handleDecline && (
								<Button
									size='small'
									startIcon={<X />}
									color='error'
									onClick={() =>
										isPending ? cancel && cancel() : handleDecline()
									}
								>
									Отмена
								</Button>
							)}
						</>
					)}
				</CardActions>
			)}
			{isPending && (
				<LinearProgress
					variant='determinate'
					value={progress}
				/>
			)}
			{handleUpdate.isPending && <LinearProgress />}
		</Card>
	)
}
